/**
 * lcd1602.c - LCD1602 驱动库
 * by Nixsawe <ziming_cool@126.com>
 * helped by @slipperstree
 * 
 * 为了简化操作逻辑，特封装此库
 * 支持 8 位、4 位总线
 */

#include "lcd1602.h"

// 写命令
void lcd1602_write_cmd(LCD1602_CMD cmd)
{
	lcd1602_waitBusy();

	// 为上升沿做准备
	LCD1602_EN = 0;

	// 设置操作方式
	LCD1602_RS = 0;	// 数据/[命令] (H/[L])
	LCD1602_RW = 0;	// 读/[写] (H/[L])

	// 传输内容
	lcd1602_write(cmd);
}

// 写数据 (到 DDRAM 或 CGRAM)
void lcd1602_write_data(LCD1602_DATA dat)
{
	lcd1602_waitBusy();

	// 为上升沿做准备
	LCD1602_EN = 0;

	// 设置操作方式
	LCD1602_RS = 1;	// [数据]/命令 ([H]/L)
	LCD1602_RW = 0;	// 读/[写] (H/[L])

	// 传输内容
	lcd1602_write(dat);
}

// 读状态 (BF 以及 AC 地址值)
LCD1602_STATE lcd1602_read_state()
{
	LCD1602_STATE dat;

	lcd1602_waitBusy();

	// 为上升沿做准备
	LCD1602_EN = 0;

	// 设置操作方式
	LCD1602_RS = 0;	// 数据/[状态] (H/[L])
	LCD1602_RW = 1;	// [读]/写 ([H]/L)

	// 传输内容
	lcd1602_read(dat);

	return dat;
}

// 读数据 (从 DDRAM 或 CGRAM)
LCD1602_DATA lcd1602_read_data()
{
	LCD1602_DATA dat;

	lcd1602_waitBusy();

	// 为上升沿做准备
	LCD1602_EN = 0;

	// 设置操作方式
	LCD1602_RS = 1;	// [数据]/状态 ([H]/L)
	LCD1602_RW = 1;	// [读]/写 ([H]/L)

	// 传输内容
	lcd1602_read(dat);

	return dat;
}

// 判断 LCD1602 繁忙状态
LCD1602_STATE lcd1602_busy()
{
	LCD1602_STATE busy;

	// 为上升沿做准备
	LCD1602_EN = 0;

	// 等待一个周期
	lcd1602_wait_nop();

	// 置 BUSY 位为高电平
	LCD1602_IO_D7 = 1;

	// 设置操作方式
	LCD1602_RS = 0;	// 数据/[状态] (H/[L])
	LCD1602_RW = 1;	// [读]/写 ([H]/L)

	// 创建上升沿
	lcd1602_rising_edge();

	// 如果设备不忙碌的话，则这个时间足够设备进行响应了
	// 设备会将 BUSY 位置 0，如果该位仍为 1，则说明设备忙碌，无法响应
	busy = LCD1602_IO_D7 == 1;

	#ifdef LCD1602_DATALINE_4_BITS
	while(busy = LCD1602_IO_D7);
	#endif

	// 下降沿
	lcd1602_falling_edge();

	return busy;
}

// 同步等待直到 LCD1602 不再繁忙
void lcd1602_waitBusy()
{
	while(lcd1602_busy())
	{
		lcd1602_wait_nop();
	}
}

LCD1602_INIT_DATA lcd1602_config = {
	// 显示两行，字体大小为 5*8
	LCD1602_XCMD_FNS(LCD1602_TWO_LINES | LCD1602_NORMAL_FONT),
	// 打开屏幕显示
	LCD1602_XCMD_DSP(LCD1602_DISPLAY_ON),
	// 输入时光标右移，屏幕固定
	LCD1602_XCMD_ENT(LCD1602_ENTRY_ADDR_INCREASE | LCD1602_ENTRY_SCREEN_HOLD)
};

// 执行初始化
// 就算定义了 LCD1602_NO_AUTO_WAIT，本函数也会等待设备完成操作
void lcd1602_init(LCD1602_INIT_DATA * p_init_data)
{
	if(p_init_data)
	{
		if(p_init_data -> FNS)
			lcd1602_config.FNS = p_init_data -> FNS;
		if(p_init_data -> DSP)
			lcd1602_config.DSP = p_init_data -> DSP;
		if(p_init_data -> ENT)
			lcd1602_config.ENT = p_init_data -> ENT;
	}

	// 设置数据总线、显示行数、字体大小
	// LCD1602_CMD_FNS(N,F)
	lcd1602_write_cmd(lcd1602_config.FNS);

	// 设置屏幕显示、光标
	// LCD1602_CMD_DSP(D,C,B)
	lcd1602_write_cmd(lcd1602_config.DSP);

	// 设置输入模式
	// LCD1602_CMD_ENT(ID,SH)
	lcd1602_write_cmd(lcd1602_config.ENT);

	// 执行清屏
	// LCD1602_CMD_CLD()
	lcd1602_write_cmd(LCD1602_CMD_CLD());
}

// 清屏
void lcd1602_cld()
{
	lcd1602_write_cmd(LCD1602_CMD_CLD());
}

// 光标回到初始位置
void lcd1602_ret()
{
	lcd1602_write_cmd(LCD1602_CMD_RET());
}

// 设置光标位置
void lcd1602_setcursor(LCD1602_DATA x, LCD1602_DATA y)
{
	if(y)
	{
		x += 0x40;
	}
	lcd1602_write_cmd(LCD1602_CMD_DDR(x));
}

// 光标左移
void lcd1602_cursor_left()
{
	lcd1602_write_cmd(LCD1602_XCMD_SFT(LCD1602_SHIFT_CURSOR | LCD1602_SHIFT_LEFT));
}

// 光标右移
void lcd1602_cursor_right()
{
	lcd1602_write_cmd(LCD1602_XCMD_SFT(LCD1602_SHIFT_CURSOR | LCD1602_SHIFT_RIGHT));
}

// 屏幕左移
void lcd1602_screen_left()
{
	lcd1602_write_cmd(LCD1602_XCMD_SFT(LCD1602_SHIFT_SCREEN | LCD1602_SHIFT_LEFT));
}

// 屏幕右移
void lcd1602_screen_right()
{
	lcd1602_write_cmd(LCD1602_XCMD_SFT(LCD1602_SHIFT_SCREEN | LCD1602_SHIFT_RIGHT));
}

// 打印字符串 (使用 NUL 截断)
void lcd1602_print(char * s)
{
	int i;
	if(!s) return;

	for(i = 0; s[i]; i ++)
	{
		lcd1602_write_data((LCD1602_DATA)s[i]);
	}
}

// 打印字符串 (使用固定长度，不会被 NUL 截断)
void lcd1602_nprint(char * s, int n)
{
	int i;
	if(!s) return;

	for(i = 0; i < n; i ++)
	{
		lcd1602_write_data((LCD1602_DATA)s[i]);
	}
}

// 在字库中构造生成对应的字符
// 直接使用 lcd1602_nprint 写数据，故而如果 I/D 的方向为自减，这个函数将无法正常工作
// 因此，调用本函数前，必须设置 I/D 方向为自增
void lcd1602_define(LCD1602_DATA char_code, LCD1602_DATA bytes[8])
{
	lcd1602_write_cmd(LCD1602_CMD_CGR(char_code << 3));
	lcd1602_nprint((char *)bytes, 8);
}
